Desktop pop-up notifications (Jelly-style toast)#31
Merged
TripleU613 merged 12 commits intoJul 1, 2026
Conversation
New `popup_notifications` sub-plugin: an in-browser toast card that appears top-right (below the header search) when a new notification arrives, modelled on the Jelly macOS notifier's look and delivery. Purely additive — it subscribes to the same `/notification/:user_id` MessageBus channel that already drives the bell counter and the notifications dropdown, and renders a card; the bell, dropdown, and read-state are untouched. Turning it off just stops the card. - Desktop only (never mounts on `site.mobileView`). - Opt-in per user, OFF by default: a "Desktop Pop Up Notifications" On/Off dropdown on the account page, stored in the `jtech_popup_notifications_enabled` user custom field; `popup_notifications_default_enabled` (default false) controls the default for users who haven't chosen. - Card layout: acting user's name on top, avatar on the left, topic title bold, then a preview of their message (fetched from the source post). - Click the card → route to the post (same as the dropdown row); click anywhere else, or wait `popup_notifications_timeout_seconds`, to dismiss. Backend is thin: registers the editable boolean custom field and exposes the effective (default-aware) value on the current-user serializer. Tests: - spec/system/popup_notifications_spec.rb — screenshots both states and proves the additive contract: OFF ⇒ the reply still creates the normal `replied` notification and NO toast; ON ⇒ same notification PLUS the toast, click-to-route, and click-outside dismiss. - spec/requests/popup_notifications_pref_spec.rb — the preference is editable, off by default, and default-aware on the serializer. Linting: stree, prettier, eslint, stylelint, ember-template-lint all clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
- Add spec/system/popup_notifications_screenshots_spec.rb: 15 screenshots across both surfaces — the account-page preference control (default off, dropdown open, set on, set off), the admin master switch, the control hidden when the master is off, a toast per notification type (reply, mention, quote, PM, like), content-shape variety (long title ellipsis, long message clamp, bell-icon fallback), and the off state (no toast). Toast shots publish crafted notifications on /notification/:id for exact, fast visual states; the real end-to-end path stays covered by the behavioral spec. - Drop redundant `type: :system` metadata (Discourse/NoSystemSpecMetadata). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
The Feature Screenshots workflow runs a fixed spec list and uploads the resulting tmp/capybara PNGs as an artifact. Add the new popup_notifications_screenshots_spec.rb so its 15 shots are captured and published for review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
The On/Off dropdown was mounted on the `user-custom-preferences` outlet, which lives on the PROFILE page — not the account page — so it never rendered (the 01–04 screenshot example failed on a missing selector). Move it to `user-preferences-account` (confirmed present on the account page in Discourse 2026.7) and save immediately on change via PUT /u/:username.json instead of relying on the account form's Save button, which does not persist arbitrary custom fields. The value is mirrored onto the current user for live toast gating and reverted on error. This save path is the one exercised by spec/requests/popup_notifications_pref_spec.rb. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
The account-page dropdown rendered "[en.jtech_popup_notifications.preference.on]" instead of "On" — YAML parses bare `on:`/`off:` keys as booleans, so those two i18n keys never existed (title/instructions resolved fine). Quote the keys. Caught by the 01 screenshot. Also switch the screenshot spec's row selection to literal "On"/"Off" instead of I18n.t, which is simpler and avoids the same trap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
- Heading line is now "Name — Action" (e.g. "pat — Liked your post"), driven by a per-type action label (config/locales client action.*). - Add a small type-icon badge on the avatar's corner; postless notifications render the type icon on its own instead of an avatar. - Decode the plugin's own `custom` notifications so they pop too and read correctly: moderator whispers (eye), flag notes (flag), and queued/pending-post approvals/rejections (check / xmark), plus the other mod-note kinds. These already publish on /notification/:id (staff_notifier + the whisper dedupe job), so the toast picks them up. - Register the badge SVG icons. - Screenshots: type shots now show the heading + badge; add whisper, flag, and pending-approved shots (16–18). Behavioral spec asserts the new name/action classes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
The behavioral spec relied on PostCreator triggering a synchronous `replied` notification, but PostAlerter runs async in system tests — so no notification fired and the toast never appeared. The screenshot spec pushed several notifications per example; under the parallel runner only the first per page delivered. Both now use the reliable pattern: one crafted MessageBus publish on /notification/:id per FRESH page load (the same delivery core uses). The client registers its subscription position at page load, before the publish, so the message is always delivered. Behavioral spec proves off→no toast, on→toast (name/action/title) + click-to-route + click-outside dismiss; screenshot gallery grows to 18 (adds whisper/flag/pending). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
After a warm page load, #post_1 can render before the browser's MessageBus poll re-establishes its subscription, so a lone publish lands before the client is listening and is missed — which dropped the 2nd+ shot in each multi-shot example under turbo. Re-publish (fresh id) until the toast (or a specific type icon) appears, in both the screenshot and behavioral specs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
Member
Author
Concurrent notifications now STACK one below another (newest on top, just below the header search), up to 3 at once; a 4th drops the oldest (bottom) card. Each card keeps its own auto-dismiss timer; clicking a card opens it and clicking anywhere else dismisses them all. The toast host became a fixed column container; cards are normal children. Tests: - popup_notifications_stacking_screenshots_spec.rb — 25 shots across single/double/triple stacks, type mixes, content shapes, and the overflow-replaces-oldest behavior. - popup_notifications_click_through_spec.rb — 30 examples: a notification about a post in a DIFFERENT topic pops the toast, and clicking it opens that topic at the post and dismisses the card (5 types × 6 target topics; batch 1 also screenshots before/after). - Both wired into the Feature Screenshots workflow. Reliability: each stack build primes the MessageBus subscription then publishes one card at a time waiting for the exact count; click-through retries the publish until the card lands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
Two fixes for the failing system_tests job: - The `liked` click-through failed on every batch because its target title "Target 1 liked" is 14 chars, under Discourse's 15-char minimum, so the topic fabrication raised. Give the target topics longer titles. - The 55 browser-based gallery examples (18 single + 25 stacking + 30 click-through) overloaded the shared, parallel system_tests job and timed out. Gate all three gallery specs behind JTECH_SCREENSHOT_GALLERY (set only in the Feature Screenshots workflow) so they generate screenshots there and skip in system_tests. Core behavior — off/on, click-to-open, dismiss — stays gated by the lean popup_notifications_spec.rb. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
The stacking screenshots run only in the single-process Feature Screenshots workflow (no per-example retry), where one flaky capture aborted the rest of a grouped example and dropped later shots. Split the 25 stacks into 25 independent examples (fresh session each) so a flake isolates to one shot and the gallery captures the rest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Js4yBxioTgsB8TkTd6HE9j
Member
Author
Member
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Desktop pop-up notifications (Jelly-style toast)
A new
popup_notificationssub-plugin: an in-browser toast card that appears in the top-right corner (just below the header search) when a new notification arrives, modelled on the Jelly macOS notifier's look and delivery.Behavior
/notification/:user_idMessageBus channel that already drives the bell counter and the notifications dropdown, and renders a card — the bell, the dropdown, and read-state are all untouched. Turning it off simply stops the card.site.mobileView)./u/:username/preferences/account), stored in thejtech_popup_notifications_enableduser custom field.popup_notifications_default_enabled(defaultfalse) controls the default for users who haven't chosen, so enabling the plugin never surprises the whole forum.popup_notifications_timeout_seconds(default 20) — dismisses it.Every code path is gated (
popup_notifications_enabled+ per-user pref + desktop) and wrapped so a malformed payload can never break the page.Settings
popup_notifications_enabledtruepopup_notifications_default_enabledfalsepopup_notifications_timeout_seconds20Tests
spec/system/popup_notifications_spec.rb— proves the additive contract end-to-end with a real reply: OFF ⇒ the reply still creates the normalrepliednotification and NO toast; ON ⇒ same notification plus the toast, click-to-route, and click-outside dismiss.spec/system/popup_notifications_screenshots_spec.rb— a 15-shot gallery across both surfaces: the account-page control (default off / dropdown open / set on / set off), the admin master switch, the control hidden when the master is off, a toast per notification type (reply, mention, quote, PM, like), content-shape variety (long-title ellipsis, long-message clamp, bell-icon fallback), and the off state.spec/requests/popup_notifications_pref_spec.rb— the preference is editable, off by default, and default-aware on the current-user serializer.Linting (
stree,prettier,eslint,stylelint,ember-template-lint) all pass locally.🤖 Generated with Claude Code